package com.nus.fellowmark.common.exception;


import cn.dev33.satoken.exception.NotLoginException;
import cn.dev33.satoken.exception.NotPermissionException;
import cn.dev33.satoken.exception.NotRoleException;
import com.google.common.collect.Lists;
import com.nus.fellowmark.common.response.ResponseEnum;
import com.nus.fellowmark.common.response.ServerResponseEntity;
import lombok.extern.slf4j.Slf4j;
import org.springframework.util.CollectionUtils;
import org.springframework.validation.ObjectError;
import org.springframework.web.bind.MethodArgumentNotValidException;
import org.springframework.web.bind.annotation.ExceptionHandler;
import org.springframework.web.bind.annotation.RestControllerAdvice;

import javax.validation.ConstraintViolation;
import javax.validation.ConstraintViolationException;
import java.util.LinkedList;
import java.util.List;
import java.util.Set;

/**
 * @author Ruoyi Chentt
 * @email chenruoyisz@gmail.com
 * @create 2023/2/11
 */
@Slf4j
@RestControllerAdvice
public class GlobalExceptionHandler {

    @ExceptionHandler(value = NotLoginException.class)
    public ServerResponseEntity<RRException> notLoginExceptionHandler(NotLoginException e) {
        printErrorInfo(e, "token not provided, user not login");
        return ServerResponseEntity.showFailMsg(ResponseEnum.USER_NOT_LOGIN.getMsg());
    }

    @ExceptionHandler(value = {NotRoleException.class, NotPermissionException.class})
    public ServerResponseEntity<RRException> notRoleExceptionHandler(NotLoginException e) {
        printErrorInfo(e, "permission denied.");
        return ServerResponseEntity.showFailMsg(ResponseEnum.NOT_PERMIT_ERROR.getMsg());
    }

    @ExceptionHandler(value = ConstraintViolationException.class)
    public ServerResponseEntity<List<String>> constraintViolationHandler(ConstraintViolationException e) {
        Set<ConstraintViolation<?>> violations = e.getConstraintViolations();
        LinkedList<String> errors = Lists.newLinkedList();
        if (!CollectionUtils.isEmpty(violations)) {
            violations.forEach(constraintViolation -> errors.add(constraintViolation.getMessage()));
        }

        printErrorInfo(e, "constraint violation");
        return ServerResponseEntity.showFailMsg(ResponseEnum.CONSTRAINT_VIOLATION.getMsg());
    }

    @ExceptionHandler(value = MethodArgumentNotValidException.class)
    public ServerResponseEntity<List<String>> methodArgumentNotValidExceptionHandler(MethodArgumentNotValidException e) {
        List<ObjectError> allErrors = e.getBindingResult().getAllErrors();
        LinkedList<String> errors = Lists.newLinkedList();
        if (!CollectionUtils.isEmpty(allErrors)) {
            allErrors.forEach(objectError -> errors.add(objectError.getDefaultMessage()));
        }

        printErrorInfo(e, "method arguments not valid");
        return ServerResponseEntity.showFailMsg(ResponseEnum.ARGS_NOT_VALID.getMsg());
    }

    @ExceptionHandler(value = NullPointerException.class)
    public ServerResponseEntity<RRException> exceptionHandler(NullPointerException e){
        printErrorInfo(e);
        return ServerResponseEntity.showFailMsg(ResponseEnum.SYS_ERROR.getMsg());
    }

    @ExceptionHandler(value = Exception.class)
    public ServerResponseEntity<RRException> exceptionHandler(Exception e) {
        printErrorInfo(e);
        return ServerResponseEntity.showFailMsg(ResponseEnum.SYS_ERROR.getMsg());
    }

    private void printErrorInfo(Exception e) {
        printErrorInfo(e, e.getMessage());
    }

    private void printErrorInfo(Exception e, String reason) {
        final StackTraceElement stackTrace = e.getStackTrace()[0];
        final String className = stackTrace.getClassName();
        final int lineNumber = stackTrace.getLineNumber();
        final String methodName = stackTrace.getMethodName();
        e.printStackTrace();

        log.error("=========== Error occurred!! ===========");
        log.error("==> Error at: ");
        log.error("==> class[{}] ==> method[{}] ==> line[{}]\n", className, methodName, lineNumber);
        log.error("===> Error reason: ");
        log.error("-> {}", reason);
        log.error("=======================================");
    }
}
